﻿using MEF_Test.Contracts;

namespace MEF_Test.Bricks.TemplateProvider
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Windows;
    using System.Xml.Serialization;

    using StronglyTypedExports;

    public class TemplateProvider : ITemplateProvider
    {
        /// <summary>
        /// The dictionary holding the skin information : For a given templateKind, it defines GUID of the DataTemplate to use.
        /// </summary>
        static private Dictionary<string, Guid> _dataTemplatesMapping;

        // a templateKind is a namespace-like string to define the fallback to retrieve the template. ex : number.ratio.ImdbRatio
        public DataTemplate GetTemplate(string templateKind)
        {
            if (_dataTemplatesMapping == null)
            {
                _dataTemplatesMapping = loadDataTemplatesMapping();
            }

            do
            {
                // if we got a perfect match at the first try :
                // everything went perfectly well : the templatekind is specifically defined for this templateKind. no further fallback was needed.
                if (_dataTemplatesMapping.ContainsKey(templateKind))
                {
                    // Here, we assume that if a skin defines a specific datatemplate to use, it exists and is correctly exported / imported. Otherwise, an ArgumentOutOfRangeException is raised.
                    return GetDataTemplateByGuid(_dataTemplatesMapping[templateKind]);
                }

                // if the templateKind is not a perfect match with an existing one within the mappings,
                // we'll try to find a default template within the exported templates for the current templateKind.
                // if a perfect match on the default template kind is found, its defaultTemplate, will be used.
                string kind = templateKind;
                var defaultTemplateForOriginalTemplateKind =
                    _dataTemplates.Where(
                        o => o.Metadata.IsDefaultTemplate && o.Metadata.DefaultTemplateKind == kind).SingleOrDefault();
                if (defaultTemplateForOriginalTemplateKind != null)
                {
                    return defaultTemplateForOriginalTemplateKind.Value;
                }

                templateKind = getTemplateKindParent(templateKind);
            }
            while (templateKind != null);
            return null;
        }

        private DataTemplate GetDataTemplateByGuid(Guid templateGuid)
        {
            var dataTemplate = this._dataTemplates.Where(b => Guid.Parse(b.Metadata.TemplateGuid).Equals(templateGuid));
            var templateCount = dataTemplate.Count();
            if (templateCount != 1)
            {
                var message = string.Format(
                    "There should be exactly one match for template GUID {0}. {1} have been found.", 
                    templateGuid, 
                    templateCount);

                throw new ArgumentOutOfRangeException(
                    "templateGuid", 
                    templateGuid, 
                    message);
            }

            return dataTemplate.First().Value;
        }

        /// <summary>
        ///  Registers a template provider to be used with a specific templateKind
        /// </summary>
        /// <param name="templateKind">the templateKind the DataTemplate should be registered with</param>
        /// <param name="templateGuid">the GUID of the exported part providing the desired template. (Found in MetaData)</param>
        public void AddTemplateKind(string templateKind, Guid templateGuid)
        {
            if (!_dataTemplatesMapping.ContainsKey(templateKind))
            {
                _dataTemplatesMapping.Add(templateKind, templateGuid);
            }
        }

        private readonly IEnumerable<Lazy<DataTemplate, IMediaHandlerTemplateExportAttribute>> _dataTemplates;


        public TemplateProvider(IEnumerable<Lazy<DataTemplate, IMediaHandlerTemplateExportAttribute>> dataTemplates)
        {
            _dataTemplates = dataTemplates;

            _dataTemplatesMapping = loadDataTemplatesMapping();
        }

        private Dictionary<string, Guid> loadDataTemplatesMapping()
        {            
            // FIXME: save and read data in a proper directory !
            Dictionary<string, Guid> retval = new Dictionary<string, Guid>();
            XmlSerializer xs = new XmlSerializer(typeof(List<SerializableKeyValuePair<string, Guid>>));
            if (new FileInfo("Skin.xml").Exists)
            {

                using (Stream s = File.OpenRead("Skin.xml"))
                {
                    var serializableMappings = (List<SerializableKeyValuePair<string, Guid>>)xs.Deserialize(s);

                    foreach (var item in serializableMappings)
                    {
                        retval.Add(item.Key, item.Value);
                    }
                }

            }
            return retval;
        }

        private void saveDataTemplatesMapping(Dictionary<string, Guid> dataTemplateMapping)
        {
            var serializableMappings = new List<SerializableKeyValuePair<string, Guid>>();
            foreach (var item in dataTemplateMapping)
            {
                serializableMappings.Add(new SerializableKeyValuePair<string, Guid> { Key = item.Key, Value = item.Value });
            }
            var xs = new XmlSerializer(typeof(List<SerializableKeyValuePair<string, Guid>>));
            using (Stream s = File.Open("Skin.xml", FileMode.Create))
            {
                xs.Serialize(s, serializableMappings);                
            }
        }


        private static string getTemplateKindParent(string templateKind)
        {
            var lastPos = templateKind.LastIndexOf('.');
            if (lastPos < 0)
            {
                return null;
            }
            return templateKind.Substring(0, lastPos);
        }
    }
}
